package com.mcxx.aspect;

import com.mcxx.aspect.annotation.BizVisitLimit;
import com.mcxx.modules.system.dto.VisitLimitDTO;
import com.mcxx.redis.client.RedisClusterUtils;
import com.mcxx.util.DateUtil;
import com.mcxx.util.IPUtil;
import com.yinhai.ta404.core.restservice.BaseRestService;
import com.yinhai.ta404.core.security.vo.UserAccountVo;
import com.yinhai.ta404.core.utils.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.*;



@Slf4j
@Aspect
@Component
public class VisitLimitAspect extends BaseRestService {
    @Autowired
    private RedisClusterUtils redisClusterUtils;
    private final static String REDIS_CACHE_SUFFIX = "FUNC:";
    private final static String REDIS_OPS_SUFFIX = "INVALID_OPS:";
    private final static String REDIS_LIMIT_SUFFIX = "LIMIT:";
    private final static long EXPIRE_TIME = 24 * 3600;

    @Pointcut("@annotation(com.mcxx.aspect.annotation.BizVisitLimit)")
    public void visitLimitPointCut(){}

    @Around("@annotation(com.mcxx.aspect.annotation.BizVisitLimit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes request = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String ip = IPUtil.getIpAddress(request.getRequest());


        BizVisitLimit limit = method.getAnnotation(BizVisitLimit.class);
        if (limit != null) {

            String key = REDIS_CACHE_SUFFIX + ip + limit.key();
            int period = limit.minPeriod();
            if(null != redisClusterUtils.get(key)) {
                this.addVisitLimitInfo(limit, ip, request);
                return null;
            }
            redisClusterUtils.set(key, new Date(), period);
        }
        return joinPoint.proceed();
    }


    private void addVisitLimitInfo(BizVisitLimit limit, String ip, ServletRequestAttributes request) {
        UserAccountVo userAccountVo = WebUtil.getCurUserAccountVo(request.getRequest());
        String visitTime = DateUtil.formatDateTime();

        String key = REDIS_CACHE_SUFFIX + REDIS_OPS_SUFFIX;
        Object objLimitRec = redisClusterUtils.hget(key, ip);
        List<VisitLimitDTO> list = new ArrayList<>();
        boolean hasSameVisit = false;
        if(null != objLimitRec) {
            list = (List<VisitLimitDTO>) objLimitRec;
            for(VisitLimitDTO visitLimit : list) {
                if(ip.equals(visitLimit.getIp()) && visitLimit.getKey().equals(limit.key())) {
                    visitLimit.addVisitTime(visitTime);
                    hasSameVisit = true;
                    boolean hasOutLimit = this.checkIfOutLimitTimes(visitLimit.getVisitTimeList(), limit.maxTimes());
                    if(hasOutLimit) {
                        String limitKey = REDIS_CACHE_SUFFIX + REDIS_LIMIT_SUFFIX;
                        visitLimit.setLimitStartTime(new Date());
                        redisClusterUtils.hset(limitKey, ip, visitLimit);
                        log.info("加入业务操作限制名单：{}", visitLimit);
                        setError("您的操作过于频繁，禁用救助系统1天，如有疑问请与管理员联系");
                    }
                    else {
                        setError(limit.msg());
                    }
                    break;
                }
            }
        }
        else if(!hasSameVisit) {
            VisitLimitDTO limitDTO = new VisitLimitDTO();
            limitDTO.setIp(ip);
            limitDTO.setKey(limit.key());
            limitDTO.setAreaCode(userAccountVo.getAreaCode());
            limitDTO.setUserLoginId(userAccountVo.getLoginId());
            limitDTO.addVisitTime(visitTime);
            list.add(limitDTO);
            setError(limit.msg());
        }
        log.info("接口高频重复操作，IP={}, key={}", ip, limit.key());
        redisClusterUtils.hset(key, ip, list, EXPIRE_TIME);
    }


    private boolean checkIfOutLimitTimes(List<String> opsTimeList, int maxTimes) {
        Map<String, Integer> map = new HashMap<>();
        int times = 1;
        for(String timeStr : opsTimeList) {
            int diff = this.minuteDiff(timeStr);
            if(diff == 0) {
                ++times;
            }
        }
        return times > maxTimes;
    }

    public int minuteDiff(String dateStr) {
        Calendar calSrc = Calendar.getInstance();
        calSrc.setTime(new Date());
        Calendar calDes = Calendar.getInstance();
        Date desDate = DateUtil.str2Date(dateStr, DateUtil.sdf);
        calDes.setTime(desDate);
        long millisDiff = getMillis(calSrc) - getMillis(calDes);
        return (int) (millisDiff / (60 * 1000));
    }

    public long getMillis(Calendar cal) {
        return cal.getTime().getTime();
    }

}
